[PANA-8578] Add support for session replay string namespaces - #428
Draft
sethfowler-datadog wants to merge 1 commit into
Draft
Conversation
sethfowler-datadog
force-pushed
the
seth.fowler/PANA-8578-add-support-for-session-replay-string-namespaces
branch
from
August 13, 2026 09:09
f072af4 to
ad0c07a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PANA-8578 Add support for session replay string namespaces
Summary
Adds string namespaces to the browser Session Replay "Change" data format. A string namespace is a separate underlying store of strings, distinct from the single flat string table the format uses today.
This PR lands the schema vocabulary and the two new change opcodes needed to create namespaces and add strings to them. It does not land the mapping mechanism that connects namespaces to the string table — that's deliberately deferred to a follow-up PR (see Scope).
Motivation
Splitting strings across multiple namespaces buys us two things:
Recording size. Stylesheets contain very different strings than the DOM. Partitioning them keeps each namespace smaller, and — crucially — keeps the indices into each namespace smaller when serialized as JSON numbers. This reduces recording size both pre- and post-compression.
Downstream transformations. Some transformations should apply to some strings but not others. For example, we may want to mask text that appeared on the page when a less-privileged user views the replay, while leaving structural text like tag names and attribute names unmasked. Partitioning those into separate namespaces makes this a namespace-level operation rather than a per-string classification problem.
Terminology
The format previously used "string table" for a single flat store. With multiple stores in play, this PR fixes the terminology and applies it consistently across every description in the schema:
Ultimately, string references remain indices into one virtual string table, working analogously to virtual memory: strings are added to whichever namespace they belong in, and regions of those namespaces are mapped into the shared string table at particular positions. This preserves the simplicity and compactness of "a string reference is just a number," while enabling later work such as rearranging recorded strings to shrink reference sizes, or dropping unmapped regions to reduce memory usage.
Changes
New change opcodes
11AddStringNamespaceChangeStringNamespaceName12AddNamespacedStringChange[StringNamespaceOrStringNamespaceReference, ...StringLiteral[]]Namespace references are assigned by creation order:
0is the default string namespace, and eachAddStringNamespaceChangetakes the next available reference. This matches how string indices already work for opcode0.New schema files
All under
schemas/session-replay/browser/changes/:add-string-namespace-change-schema.json—AddStringNamespaceChangeadd-namespaced-string-change-schema.json—AddNamespacedStringChangestring-references/string-literal-schema.json—StringLiteralstring-references/namespaced-string-literal-schema.json—NamespacedStringLiteralstring-references/string-namespace-name-schema.json—StringNamespaceNamestring-references/string-namespace-reference-schema.json—StringNamespaceReferencestring-references/string-namespace-or-string-namespace-reference-schema.json—StringNamespaceOrStringNamespaceReferenceThe naming now rhymes across both reference kinds:
StringLiteralStringReferenceStringOrStringReferenceStringNamespaceNameStringNamespaceReferenceStringNamespaceOrStringNamespaceReferenceExplicit vs. recording representations
Real recordings replace every string with a string reference, and will always address namespaces by numeric reference. The literal forms exist so the format can also express a more explicit representation — useful both as input to an encoder that builds the tables, and as output when presenting a recording in a form a human can read.
Accordingly,
StringOrStringReferencegains a third variant,NamespacedStringLiteral, which pairs a string with the namespace it belongs to:{ "namespace": "user-text", "string": "Hello" }Both properties are mandatory and no others are permitted.
namespaceaccepts either form, so the same value can be written{ "namespace": 3, "string": "Hello" }in compact form.Library changes
ChangeTypegainsAddStringNamespace(11) andAddNamespacedString(12). These are guarded by the existingChangeTypeIdhelper, so a constant that disagrees with the schema is a compile error.StringNamespaceId, alongside the existing brandedNodeId/StringId/StyleSheetId, so namespace references can't be silently confused with the other numeric identifier spaces.lib/cjsandlib/esm.Scope
The mapping mechanism is deliberately not included in this PR. Nothing here connects a namespace to a region of the string table, so namespaces are declared and populated but not yet addressable via
StringReference. This is the subtle part of the design and gets its own PR.Compatibility
Additive. Existing opcodes keep their discriminators — the two new ones are appended as
11and12— so previously recorded data is unaffected and existing producers need no changes.One consumer-visible note for TypeScript users:
StringOrStringReferencewidens fromstring | StringReferencetoStringLiteral | NamespacedStringLiteral | StringReference. Code that exhaustively narrows this type will need a case for the object variant. In practice recordings will never contain one, but the type is now wider.